package tuding.android.bigplanettracks.tracks;

import java.util.ArrayList;
import java.util.Collections;

import tuding.android.bigplanettracks.BigPlanet;
import tuding.android.bigplanettracks.maps.tuding.ActionNotes;

import android.location.Location;
import android.util.Log;

/**
 * @author TYTung
 * @version 0.2
 */
public class TrackAnalyzer {
	
	public static final int measureVersion = 1;
	
	private String trackName;
	private String trackDescription;
	private String startGMTTime;
	private ArrayList<Location> locationList;
	private	String trackSource;
	private int trackID;
	
	private ArrayList<Double> altitudeList;
	private ArrayList<Float> speedList;
	private long totalTime;
	private float totalDistance;
	private float averageSpeed;
	private float maximumSpeed;
	private double minAltitude;
	private double maxAltitude;
	private int trackPoints;
	private int actionNotesNum;
	private int actionPhotoNum;
	
	public TrackAnalyzer(String trackName, String trackDescription, String startGMTTime, 
			ArrayList<Location> locationList, String trackSource) {
		this.trackName = trackName;
		this.trackDescription = trackDescription;
		this.startGMTTime = startGMTTime;
		this.locationList = locationList;
		this.trackSource = trackSource;
		
		altitudeList = new ArrayList<Double>();
		speedList = new ArrayList<Float>();
//		totalTime = 0;
//		totalDistance = 0f;
//		averageSpeed = 0f;
//		maximumSpeed = 0f;
//		minAltitude = 0d;
//		maxAltitude = 0d;
		trackPoints = locationList.size();
		this.trackID = 0;
	}
	
	public TrackAnalyzer(String trackName, String trackDescription, String startGMTTime, 
			ArrayList<Location> locationList) {
		this(trackName, trackDescription, startGMTTime, locationList, null);
	}
	
	public TrackAnalyzer(String trackName, String trackDescription, String startGMTTime, 
			ArrayList<Location> locationList, int trackID) {
		this(trackName, trackDescription, startGMTTime, locationList, null);
		this.trackID = trackID;
	}
	
	public void analyze(boolean hasLog) {
		if (hasLog)
			Log.i("Message", "Perform TrackAnalyzer");
		if (locationList.size() > 1) {
			computeTotalTime();
			computeTotalDistance();
			computeAverageSpeed();
			computeMaximumSpeed();
			computeAltitude();
		}
		Log.d("DEL", "trackid is "+trackID);
		computeActionsNum(); 
		//computeActionPhotoNum();

	}
	
	public void computeActionsNum() {
		if(trackID != 0) {
			ActionNotes aN = new ActionNotes();
			actionNotesNum = aN.getActionNotesNumByTrackID(trackID);
			actionPhotoNum = aN.getActionPhotoNumByTrackID(trackID);
			aN.close();
		}
	}
	
	public void computeActionNotesNum() {
		if(trackID != 0) {
			ActionNotes aN = new ActionNotes();
			actionNotesNum = aN.getActionNotesNumByTrackID(trackID);
			Log.d("DEL", "actionNotesNum is "+actionNotesNum);
			aN.close();
		}
	}
	
    public void computeActionPhotoNum() {
    	if(trackID != 0) {
    		ActionNotes aN = new ActionNotes();
    		actionPhotoNum = aN.getActionPhotoNumByTrackID(trackID);
			aN.close();
    	}
	}
	
	public void analyzeAndUpdate(long trackID) {
		Log.i("Message", "trackName="+trackName);
		Log.i("Message", "trackDescription="+trackDescription);
		Log.i("Message", "trackStartGMTTime="+startGMTTime);
		analyze(true);
		BigPlanet.DBAdapter.open();
		BigPlanet.DBAdapter.updateTrack(trackID, totalTime, totalDistance, 
				averageSpeed, maximumSpeed, minAltitude, maxAltitude, trackPoints, measureVersion, actionNotesNum, actionPhotoNum);
		Log.i("Message", "measureVersion has been updated: "+measureVersion);
	}

	public void analyzeAndSave() {
		Log.i("Message", "trackName="+trackName);
		Log.i("Message", "trackDescription="+trackDescription);
		Log.i("Message", "trackStartGMTTime="+startGMTTime);

		BigPlanet.DBAdapter.open();
		long trackID = BigPlanet.DBAdapter.insertTrack(trackName, trackDescription, startGMTTime, locationList, trackSource);
		Log.i("Message", "insertTrack() finished");
		
		analyze(true);
		
		BigPlanet.DBAdapter.updateTrack(trackID, totalTime, totalDistance, 
				averageSpeed, maximumSpeed, minAltitude, maxAltitude, trackPoints, measureVersion, actionNotesNum, actionPhotoNum);
		Log.i("Message", "Insert a new track successfully");
		Log.i("Message", "-------------------------------");
	}
	
	
	// this code will filter those point which is on the same line with the former point
	public void analyzeAndSave_filter() {
		ArrayList<Location> locationList_filter = new ArrayList<Location>();
		Location locA = locationList.get(0);
		Location locB = locationList.get(1);
		Location locC;
		int intvalBC = 0;
		locationList_filter.add(locA);
		locationList_filter.add(locB);
		for(int i=2; i<locationList.size(); i++) {
			locC = locationList.get(i);
			if(intvalBC > 8) {
				intvalBC = 0;
				locA = locB;
				locB = locC;
				locationList_filter.add(locC);	
			} else if(isClosing(locA, locB, locC, 0.2)) {
				// replace B with C in list 
				locationList_filter.remove(locationList_filter.size()-1);
				//locB = locC;
				locationList_filter.add(locC);
				intvalBC++;
			} else {
				intvalBC = 0;
				locA = locB;
				locB = locC;
				locationList_filter.add(locC);
			}
			Log.i("PARSE", "i is "+i+" locationList_filter.size is "+locationList_filter.size());
		}
		BigPlanet.DBAdapter.open();
		Log.i("PARSE", "old locationList size is "+locationList.size());
		Log.i("PARSE", "new locationlist size is "+locationList_filter.size());
		long trackID = BigPlanet.DBAdapter.insertTrack(trackName, trackDescription, startGMTTime, locationList_filter, trackSource);
		locationList.clear();
		Log.i("PARSE", "locationList size after clear "+locationList.size());
		locationList = locationList_filter;
		Log.i("PARSE", "locationList size after assign "+locationList.size());
		analyze(true);
		Log.i("PARSE", "totalTime "+totalTime+" totalDistance "+totalDistance+" averageSpeed "+averageSpeed+" trackPoints "+trackPoints);
		BigPlanet.DBAdapter.updateTrack(trackID, totalTime, totalDistance, 
				averageSpeed, maximumSpeed, minAltitude, maxAltitude, locationList.size(), measureVersion, actionNotesNum, actionPhotoNum);
		
	}
	
	
	public static boolean isClosing(Location A, Location B, Location C, double threshold) {
		boolean rc = false;
		if(threshold > Math.abs(angle(A, B) - angle(B, C))) rc = true; // tan(5)
		Log.i("TRACK", "angle diff is "+Math.abs(angle(A, B) - angle(B, C)));
		return rc;
	}
	
	private static double angle (Location A, Location B) {
		if(A == null || B == null) return 100; // which should be greater than threshold to ensure current point be saved
		double angle = A.getLongitude() - B.getLongitude();
		if(angle != 0)
		    angle = (A.getLatitude() - B.getLatitude()) / angle;
		return angle;
	}
	
	
	private void computeTotalTime() {
		Location firstLocation = locationList.get(0);
		Location lastLocation = locationList.get(locationList.size()-1);
		long firstTimePoint = firstLocation.getTime();
		long lastTimePoint = lastLocation.getTime();
		totalTime = lastTimePoint-firstTimePoint;
		if (totalTime < 0)
			totalTime = 0;
	}
	
	private void computeTotalDistance() {
		totalDistance = 0;
		float speed;
		for (int i=0; i<locationList.size(); i++) {
			Location location = locationList.get(i);
			altitudeList.add(location.getAltitude());
			if (i >= 1) {
				Location previous_location = locationList.get(i-1);
				float distance = location.distanceTo(previous_location);
//				if (distance <= 10) {
					totalDistance = totalDistance + distance;
//				} else {
//					totalDistance = totalDistance + 0;
//				}
				long time = location.getTime()-previous_location.getTime();
//				System.out.println("ms M: "+time+"\t"+distance);
				if (distance == 0 || time == 0) {
					speed = 0f;
				} else {
					speed = (distance / (time/1000)) *3600/1000; // km/hr
				}
				speedList.add(speed);
			}
		}
		// filter out the possible incorrect speed
		ArrayList<Float> candicateSpeedList = new ArrayList<Float>();
		for (int i=0; i<speedList.size(); i++) {
//			System.out.println(i+"\t"+speedList.get(i));
			if (i >= 2) {
				float nextSpeed = speedList.get(i);
				float previousSpeed = (speedList.get(i-1)+speedList.get(i-2))/2;
				if (Math.abs(nextSpeed - previousSpeed) < 1.5) { // magic number
					candicateSpeedList.add(nextSpeed);
				}
			}
		}
		if (candicateSpeedList.size() > 0) {
			speedList.clear();
			speedList.addAll(candicateSpeedList);
		}
	}
	
	private void computeAverageSpeed() {
		if (totalTime > 0)
			averageSpeed = (totalDistance / (totalTime/1000)) *3600/1000; // km/hr
		else
			averageSpeed = 0;
	}
	
	private void computeMaximumSpeed() {
		if (speedList.size() > 0) {
			Collections.sort(speedList);
			maximumSpeed = speedList.get(speedList.size()-1);
		} else {
			maximumSpeed = 0;
		}
	}
	
	private void computeAltitude() {
		if (altitudeList.size() > 0) {
			Collections.sort(altitudeList);
			minAltitude = altitudeList.get(0);
			maxAltitude = altitudeList.get(altitudeList.size()-1);
		}
	}
	
	public long getTotalTime() { 
		return totalTime;
	}
	
	public float getTotalDistance() {
		return totalDistance;
	}
	
	public float getAverageSpeed() {
		return averageSpeed;
	}
	
	public float getMaximumSpeed() {
		return maximumSpeed;
	}
	
	public double getMinAltitude() {
		return minAltitude;
	}
	
	public double getMaxAltitude() {
		return maxAltitude;
	}
	
	public int getTrackPoints() {
		return locationList.size();
	}
	
}
